Static Site Generator

Professional, accessible, SEO-optimized websites

Starting...
0
Pages
0
Images
0
Size
WCAG 2.1 AA
Full SEO
WebP Images
Mobile Ready

${title}

${content}
`; } async function processData(data) { updateProgress(50, 'Converting images...'); // Build image map with converted filenames const imageMap = {}; const convertedImages = []; for (let i = 0; i < data.images.length; i++) { const img = data.images[i]; updateProgress(50 + (i / data.images.length) * 20, 'Converting image ' + (i + 1) + '...'); let finalImg = { ...img }; if (img.extension === 'jpg' || img.extension === 'jpeg') { const converted = await convertToWebP(img.content, img.contentType); if (converted) { finalImg.content = converted.content; finalImg.filename = img.filename.replace(/\.(jpg|jpeg)$/i, '.webp'); finalImg.converted = true; } } convertedImages.push(finalImg); // Map original URL and filename to converted image imageMap[img.original] = finalImg; imageMap[img.absolute] = finalImg; imageMap[img.filename] = finalImg; const cleanName = img.original.split('/').pop().split('?')[0]; if (cleanName) imageMap[cleanName] = finalImg; } // Process logo if (data.logo) { let logoImg = { ...data.logo }; if (data.logo.contentType && (data.logo.contentType.includes('jpeg') || data.logo.contentType.includes('jpg'))) { const converted = await convertToWebP(data.logo.content, data.logo.contentType); if (converted) { logoImg.content = converted.content; logoImg.filename = 'logo.webp'; } } data.logo = logoImg; } updateProgress(70, 'Generating pages...'); // Build navigation const nav = data.pages.length > 1 ? data.pages.slice(0, 8).map((p, i) => { const href = i === 0 ? 'index.html' : p.filename; const label = p.title.length > 25 ? p.title.substring(0, 22) + '...' : p.title; return '
  • ' + escapeHtml(label) + '
  • '; }).join('') : ''; // Generate pages const pages = data.pages.map((page, idx) => ({ filename: idx === 0 ? 'index.html' : page.filename, html: generateTemplate(page, data, nav, imageMap, idx === 0) })); updateProgress(85, 'Creating ZIP...'); const zip = new JSZip(); // Add HTML pages for (const page of pages) { zip.file(page.filename, page.html); } // Add images const imagesFolder = zip.folder('images'); for (const img of convertedImages) { imagesFolder.file(img.filename, new Uint8Array(img.content)); } // Add logo if (data.logo && data.logo.content) { imagesFolder.file(data.logo.filename, new Uint8Array(data.logo.content)); } // Generate comprehensive sitemap const baseUrl = (data.baseUrl || '').replace(/\/$/, ''); const today = new Date().toISOString().split('T')[0]; const sitemap = '\n' + '\n' + pages.map((p, i) => ' \n' + ' ' + baseUrl + '/' + (i === 0 ? '' : p.filename) + '\n' + ' ' + today + '\n' + ' monthly\n' + ' ' + (i === 0 ? '1.0' : '0.8') + '\n' + ' ' ).join('\n') + '\n'; zip.file('sitemap.xml', sitemap); // Robots.txt zip.file('robots.txt', 'User-agent: *\nAllow: /\n\nSitemap: ' + baseUrl + '/sitemap.xml'); // .htaccess for clean URLs const htaccess = '# Enable Rewrite Engine\n' + 'RewriteEngine On\n' + 'RewriteBase /\n\n' + '# Remove .html extension\n' + 'RewriteCond %{REQUEST_FILENAME} !-f\n' + 'RewriteCond %{REQUEST_FILENAME} !-d\n' + 'RewriteCond %{REQUEST_FILENAME}.html -f\n' + 'RewriteRule ^(.+)$ $1.html [L,QSA]\n\n' + '# Security headers\n' + '\n' + ' Header set X-Content-Type-Options "nosniff"\n' + ' Header set X-Frame-Options "SAMEORIGIN"\n' + ' Header set X-XSS-Protection "1; mode=block"\n' + '\n\n' + '# Compression\n' + '\n' + ' AddOutputFilterByType DEFLATE text/html text/css application/javascript\n' + '\n\n' + '# Caching\n' + '\n' + ' ExpiresActive On\n' + ' ExpiresByType image/webp "access plus 1 year"\n' + ' ExpiresByType image/png "access plus 1 year"\n' + ' ExpiresByType text/css "access plus 1 month"\n' + '\n'; zip.file('.htaccess', htaccess); updateProgress(95, 'Finalizing...'); zipBlob = await zip.generateAsync({ type: 'blob', compression: 'DEFLATE', compressionOptions: { level: 6 } }); return { pages: pages.length, images: convertedImages.length + (data.logo ? 1 : 0), size: zipBlob.size }; } async function generate() { let url = $('urlInput').value.trim(); if (!url) { showMessage('Please enter a URL', 'error'); return; } if (!url.startsWith('http://') && !url.startsWith('https://')) url = 'https://' + url; try { new URL(url); } catch (e) { showMessage('Invalid URL format', 'error'); return; } $('message').classList.remove('active'); $('generateBtn').disabled = true; $('featuresSection').style.display = 'none'; $('progressSection').classList.add('active'); updateProgress(10, 'Connecting...'); try { const response = await fetch('/api/generate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url }) }); updateProgress(30, 'Scraping website...'); const data = await response.json(); if (!data.success) throw new Error(data.error || 'Failed to scrape website'); if (!data.pages || data.pages.length === 0) throw new Error('No pages found'); const stats = await processData(data); updateProgress(100, 'Complete!'); $('statPages').textContent = stats.pages; $('statImages').textContent = stats.images; $('statSize').textContent = formatSize(stats.size); setTimeout(() => { $('progressSection').classList.remove('active'); $('resultsSection').classList.add('active'); }, 500); } catch (error) { showMessage(error.message, 'error'); $('progressSection').classList.remove('active'); $('featuresSection').style.display = 'grid'; $('generateBtn').disabled = false; } } function download() { if (zipBlob) { const a = document.createElement('a'); a.href = URL.createObjectURL(zipBlob); a.download = 'static-site.zip'; a.click(); URL.revokeObjectURL(a.href); } } $('generateBtn').addEventListener('click', generate); $('urlInput').addEventListener('keypress', e => { if (e.key === 'Enter') generate(); }); $('downloadBtn').addEventListener('click', download); $('resetLink').addEventListener('click', e => { e.preventDefault(); reset(); });